/**
 * Copyright 2012 Pablo Danilo Aliscioni
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package ar.com.pabloaliscioni.cloud.core.storage.providers.amazon;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.Set;

import ar.com.pabloaliscioni.cloud.api.exceptions.ServiceException;
import ar.com.pabloaliscioni.cloud.core.configuration.ProviderConfiguration;
import ar.com.pabloaliscioni.cloud.core.storage.BaseStorageClient;

import com.amazonaws.AmazonClientException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.CreateBucketRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.microsoft.windowsazure.services.core.ConfigurationException;

/**
 * Implementation of the IStorageClient interface for Amazon S3 Storage service
 * 
 * @author Pablo Danilo Aliscioni
 * @since 1.0
 */
public class AmazonS3StorageClient extends BaseStorageClient
{
	private String accessKey;
	private String secretKey;
	private boolean useSSL;
	
	private AmazonS3 serviceClient;
			
	@Override
	public void configure(ProviderConfiguration providerConfiguration)
	{
		try
		{
			this.setProviderName(providerConfiguration.getName());
			
			accessKey = providerConfiguration.getConfigurationValue("Access-Key");
			secretKey = providerConfiguration.getConfigurationValue("Secret-Key");
			useSSL = Boolean.parseBoolean(providerConfiguration.getConfigurationValue("Use-SSL"));
		}
		catch (Exception e)
		{
			throw new ConfigurationException(e);
		}
	}
	
	private AmazonS3 getServiceClient()
	{
		try
		{
			if(serviceClient == null)
			{
				ClientConfiguration clientConfiguration = new ClientConfiguration();
				
				clientConfiguration.setProtocol(useSSL ? Protocol.HTTPS : Protocol.HTTP);
				
				serviceClient = new AmazonS3Client(new BasicAWSCredentials(accessKey,secretKey), clientConfiguration);
			}
			
			return serviceClient;
		}
		catch (Exception e)
		{
			throw new ConfigurationException(e);
		}
	}
		
	// API Implementation
	
	@Override
	public boolean containerExists(String containerName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		try
		{
			return getServiceClient().doesBucketExist(URLEncoder.encode(containerName,"UTF-8"));
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public void createContainer(String containerName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		try
		{
			CreateBucketRequest request = new CreateBucketRequest(URLEncoder.encode(containerName,"UTF-8"));
			
			request.withCannedAcl(CannedAccessControlList.Private);
			
			getServiceClient().createBucket(request);
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public void deleteContainer(String containerName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		try
		{
			getServiceClient().deleteBucket(URLEncoder.encode(containerName,"UTF-8"));
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public void deleteFile(String containerName, String fileName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		if(!isValidFileName(fileName))
			throw new IllegalArgumentException("Invalid file name");
		
		try
		{
			getServiceClient().deleteObject(URLEncoder.encode(containerName,"UTF-8"), URLEncoder.encode(fileName,"UTF-8"));
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public boolean fileExists(String containerName, String fileName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		if(!isValidFileName(fileName))
			throw new IllegalArgumentException("Invalid file name");
		
		try
		{
			try
			{
		        getServiceClient().getObjectMetadata(URLEncoder.encode(containerName,"UTF-8"), URLEncoder.encode(fileName,"UTF-8"));
		        
		        return true;
		    }
			catch (AmazonS3Exception e)
			{
		        if (e.getStatusCode() == 404)
		        {
		        	return false;
		        }
		        else
		        {
		            throw e;   
		        }
		    }
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public byte[] getFileContent(String containerName, String fileName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		if(!isValidFileName(fileName))
			throw new IllegalArgumentException("Invalid file name");
		
		try
		{
			S3Object s3Object = getServiceClient().
					getObject(URLEncoder.encode(containerName,"UTF-8"), URLEncoder.encode(fileName,"UTF-8"));
			
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			
			byte[] tmp = new byte[4096];
			int ret = 0;
			
			while((ret = s3Object.getObjectContent().read(tmp)) > 0)
			{
				outputStream.write(tmp, 0, ret);
			}	
			
			return outputStream.toByteArray();
		}
		catch (AmazonClientException | IOException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public String getFileContentType(String containerName, String fileName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		if(!isValidFileName(fileName))
			throw new IllegalArgumentException("Invalid file name");
		
		try
		{
	        ObjectMetadata metadata = getServiceClient().
	        		getObjectMetadata(URLEncoder.encode(containerName,"UTF-8"), URLEncoder.encode(fileName,"UTF-8"));
	        
	        return metadata.getContentType();
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public Set<String> listContainers()
	{
		try
		{
			Set<String> containers = new HashSet<String>();
			
			for (Bucket item : getServiceClient().listBuckets())
			{
				containers.add(item.getName());
			}
			
			return containers;
		}
		catch (AmazonClientException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public Set<String> listFiles(String containerName)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		try
		{
			Set<String> files = new HashSet<String>();
			
			for (S3ObjectSummary item : getServiceClient()
					.listObjects(URLEncoder.encode(containerName,"UTF-8"),"").getObjectSummaries())
			{
				files.add(item.getKey());
			}
			
			return files;
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}

	@Override
	public void saveFile(String containerName, String fileName, byte[] fileContent)
	{
		saveFile(containerName, fileName, fileContent, "application/octet-stream");
	}

	@Override
	public void saveFile(String containerName, String fileName,	byte[] fileContent, String contentType)
	{
		if(!isValidContainerName(containerName))
			throw new IllegalArgumentException("Invalid container name");
		
		if(!isValidFileName(fileName))
			throw new IllegalArgumentException("Invalid file name");
		
		if(fileContent == null)
			throw new IllegalArgumentException("Invalid file content");
		
		if(contentType == null || contentType.equals("") || !contentType.contains("/"))
			throw new IllegalArgumentException("Invalid content type");
		
		try
		{
			ObjectMetadata metadata = new ObjectMetadata();
			
			metadata.setContentLength(fileContent.length);
			metadata.setContentType(contentType);
			
			getServiceClient().putObject(new PutObjectRequest(URLEncoder.encode(containerName,"UTF-8"), 
					URLEncoder.encode(fileName,"UTF-8"), new ByteArrayInputStream(fileContent), metadata));
		}
		catch (AmazonClientException | UnsupportedEncodingException e)
		{
			throw new ServiceException(e);
		}
	}
	
	@Override
	public boolean isValidContainerName(String containerName)
	{
		if (containerName == null || containerName.length() < 3 || containerName.length() > 63 || 
				!containerName.toLowerCase().equals(containerName) || containerName.endsWith("-") || containerName.endsWith(".") ||
				containerName.contains("_") || containerName.contains("!") || containerName.contains("@") || 
				containerName.contains("#") || containerName.contains("..") || containerName.contains("-.") ||
				containerName.contains(".-") || containerName.contains(":") || containerName.contains(";") )
            return false;
		
		return true;
	}
    
	@Override
	public boolean isValidFileName(String fileNname)
	{
		if (fileNname == null || fileNname.length() == 0 || fileNname.length() > 1024 || fileNname.contains("/"))
			return false;
		
		return true;
	}
}
